/*
 * Shandong University — School of Cyber Science and Technology
 * Cryptography Engineering Course (2025)
 *
 * Course Project Contribution
 * Contributor: ShaoYan Zhang, XiaoKai Gong, YiMing Zhou, HeYang Ding
 * Instructor:  Weijia Wang
 *
 * Description:
 *   This code is part of the official output of the 2025 Cryptography Engineering
 *   course at the School of Cyber Science and Technology, Shandong University.
 *   It reflects the project work and research conducted by the contributor(s)
 *   under the supervision of the instructor.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <securec.h>
#include <pthread.h>
#include <sys/time.h>
#include "bsl_uio.h"
#include "app_utils.h"
#include "app_errno.h"
#include "app_help.h"
#include "app_print.h"
#include "app_opt.h"
#include "app_provider.h"
#include "app_sm.h"
#include "bsl_sal.h"
#include "sal_file.h"
#include "ui_type.h"
#include "bsl_ui.h"
#include "bsl_errno.h"
#include "crypt_eal_rand.h"
#include "crypt_eal_kdf.h"
#include "crypt_algid.h"
#include "crypt_errno.h"
#include "crypt_params_key.h"
#include "crypt_eal_cipher.h"
#include "crypt_eal_mac.h"
#include "app_speed.h"

// Supported cipher algorithm list
static const HITLS_CipherList g_cIdList[] = {
    {CRYPT_CIPHER_AES128_CBC, "aes-128-cbc"},
    {CRYPT_CIPHER_AES192_CBC, "aes-192-cbc"},
    {CRYPT_CIPHER_AES256_CBC, "aes-256-cbc"},
    {CRYPT_CIPHER_AES128_CTR, "aes-128-ctr"},
    {CRYPT_CIPHER_AES192_CTR, "aes-192-ctr"},
    {CRYPT_CIPHER_AES256_CTR, "aes-256-ctr"},
    {CRYPT_CIPHER_AES128_ECB, "aes-128-ecb"},
    {CRYPT_CIPHER_AES192_ECB, "aes-192-ecb"},
    {CRYPT_CIPHER_AES256_ECB, "aes-256-ecb"},
    {CRYPT_CIPHER_AES128_GCM, "aes-128-gcm"},
    {CRYPT_CIPHER_AES192_GCM, "aes-192-gcm"},
    {CRYPT_CIPHER_AES256_GCM, "aes-256-gcm"},
    {CRYPT_CIPHER_AES128_CFB, "aes-128-cfb"},
    {CRYPT_CIPHER_AES192_CFB, "aes-192-cfb"},
    {CRYPT_CIPHER_AES256_CFB, "aes-256-cfb"},
    {CRYPT_CIPHER_AES128_OFB, "aes-128-ofb"},
    {CRYPT_CIPHER_AES192_OFB, "aes-192-ofb"},
    {CRYPT_CIPHER_AES256_OFB, "aes-256-ofb"},
    {CRYPT_CIPHER_SM4_CBC, "sm4-cbc"},
    {CRYPT_CIPHER_SM4_ECB, "sm4-ecb"},
    {CRYPT_CIPHER_SM4_CTR, "sm4-ctr"},
    {CRYPT_CIPHER_SM4_GCM, "sm4-gcm"},
    {CRYPT_CIPHER_SM4_CFB, "sm4-cfb"},
    {CRYPT_CIPHER_SM4_OFB, "sm4-ofb"},
};

// Supported MAC algorithm list
static const HITLS_MacList g_mIdList[] = {
    {CRYPT_MAC_HMAC_MD5, "md5"},
    {CRYPT_MAC_HMAC_SM3, "sm3"},
    {CRYPT_MAC_HMAC_SHA1, "sha1"},
    {CRYPT_MAC_HMAC_SHA224, "sha224"},
    {CRYPT_MAC_HMAC_SHA256, "sha256"},
    {CRYPT_MAC_HMAC_SHA384, "sha384"},
    {CRYPT_MAC_HMAC_SHA512, "sha512"},
    {CRYPT_MAC_HMAC_SHA3_224, "sha3-224"},
    {CRYPT_MAC_HMAC_SHA3_256, "sha3-256"},
    {CRYPT_MAC_HMAC_SHA3_384, "sha3-384"},
    {CRYPT_MAC_HMAC_SHA3_512, "sha3-512"},
};

// Command line option enumeration
typedef enum {
    HITLS_APP_OPT_CIPHER_ALG = 2,
    HITLS_APP_OPT_MAC,
    HITLS_APP_OPT_ENC,
    HITLS_APP_OPT_DEC,
    HITLS_APP_OPT_SECONDS,
    HITLS_APP_OPT_BYTES,
    HITLS_APP_OPT_AUTO_BLOCK,
    HITLS_APP_OPT_THREADS,
} HITLS_OptType;

// Algorithm test command option structure
typedef struct {
    uint32_t version;
    int32_t cipherId;
    int32_t macId;
    int32_t encTag;
    int32_t seconds;
    int32_t bytes;
    bool autoBlock;
    int32_t threads;
} SpeedCmdOpt;

// Thread resource structure
typedef struct {
    const SpeedCmdOpt* opt;
    int blockSize;
    int threadId;
    uint64_t threadOps;
    size_t threadTotalBytes;
    double threadElapsed;
    unsigned char key[MAX_KEY_LEN];
    unsigned char iv[IV_LENGTH];
    unsigned char* in;
    unsigned char* out;
    CRYPT_EAL_CipherCtx* cipherCtx;
    unsigned char mac[MAX_MAC_LEN];
    unsigned char* data;
    CRYPT_EAL_MacCtx* macCtx;
} ThreadResource;

// Command line option definition
static const HITLS_CmdOption g_speedOpts[] = {
    {"help", HITLS_APP_OPT_HELP, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Display this function summary"},
    {"cipher", HITLS_APP_OPT_CIPHER_ALG, HITLS_APP_OPT_VALUETYPE_STRING, "Cipher algorithm (e.g., aes-128-cbc)"},
    {"mac", HITLS_APP_OPT_MAC, HITLS_APP_OPT_VALUETYPE_STRING, "MAC algorithm (e.g., sha256)"},
    {"enc", HITLS_APP_OPT_ENC, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Encryption operation"},
    {"dec", HITLS_APP_OPT_DEC, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Decryption operation"},
    {"seconds", HITLS_APP_OPT_SECONDS, HITLS_APP_OPT_VALUETYPE_UINT, "Run benchmarks for N seconds (default: 3)"},
    {"bytes", HITLS_APP_OPT_BYTES, HITLS_APP_OPT_VALUETYPE_UINT, "Custom block size in bytes (e.g., 1024)"},
    {"auto-block", HITLS_APP_OPT_AUTO_BLOCK, HITLS_APP_OPT_VALUETYPE_NO_VALUE,
        "Auto test multiple block sizes (16/64/256/1024/8192/16384 bytes)"},
    {"threads", HITLS_APP_OPT_THREADS, HITLS_APP_OPT_VALUETYPE_UINT,
        "Number of threads for parallel test (default: 1)"},
    {NULL}
};

// Global variables
static struct timeval g_startTimeval;
double throughputs[TEST_BLOCK_SIZES_LEN] = {0.0};

// Function declarations
static int32_t HandleOpt(SpeedCmdOpt* speedOpt);
static void ValidateThreadsParam(SpeedCmdOpt* speedOpt, const char* val);
static int32_t ParseSpeedOpts(SpeedCmdOpt* speedOpt);
static int32_t ValidateSpeedOpts(SpeedCmdOpt* speedOpt);
static void RunCipherBenchmark(const SpeedCmdOpt* opt);
static void RunCipherSingleBlockTest(const SpeedCmdOpt* opt, int blockSize, int index);
static void RunCipherAutoBlockTest(const SpeedCmdOpt* opt);
static int InitCipherResources(ThreadResource* res);
static int GetCipherKeyLen(int32_t cipherId);
static void FreeCipherResources(ThreadResource* res);
static void* CipherThreadWork(void* arg);
static void RunMacBenchmark(const SpeedCmdOpt* opt);
static void RunMacSingleBlockTest(const SpeedCmdOpt* opt, int blockSize, int index);
static void RunMacAutoBlockTest(const SpeedCmdOpt* opt);
static int InitMacResources(ThreadResource* res);
static uint32_t GetMacKeyLen(int32_t macId);
static void FreeMacResources(ThreadResource* res);
static void* MacThreadWork(void* arg);
static void AggregateThreadResults(const SpeedCmdOpt* opt, ThreadResource* res,
                                   double* totalSpeed, uint64_t* totalOps, double* totalElapsed);
static int32_t GetCipherId(const char* name);
static int32_t GetHMacId(const char* mdName);
static const char* GetCipherName(int32_t cipherId);
static const char* GetMacName(int32_t macId);
static void PrintCipherAlgList(void);
static void PrintHMacAlgList(void);
static void TimeF(int mode);
static bool CheckBlockSize(int cipherId, int blockSize);

// Main function
int32_t HITLS_SpeedMain(int argc, char* argv[])
{
    int32_t speedret = -1;
    SpeedCmdOpt speedOpt = {1, 0, 0, 1, 0, 0, false, 1};

    speedret = HITLS_APP_OptBegin(argc, argv, g_speedOpts);
    if (speedret != HITLS_APP_SUCCESS) {
        AppPrintError("Failed to init option parser\n");
        goto EXIT;
    }
    
    if ((speedret = HandleOpt(&speedOpt)) != HITLS_APP_SUCCESS) {
        goto EXIT;
    }
    
    if (speedOpt.seconds <= 0) {
        AppPrintError("Invalid seconds: %d (must be positive)\n", speedOpt.seconds);
        speedret = HITLS_APP_OPT_VALUE_INVALID;
        goto EXIT;
    }
    
    if (speedOpt.threads < 1 || speedOpt.threads > MAX_THREADS) {
        AppPrintError("Invalid thread count: %d (must be 1-%d)\n", speedOpt.threads, MAX_THREADS);
        speedret = HITLS_APP_OPT_VALUE_INVALID;
        goto EXIT;
    }
    
    if (speedOpt.bytes > 0 && speedOpt.cipherId != 0 && !CheckBlockSize(speedOpt.cipherId, speedOpt.bytes)) {
        AppPrintError("Block size %d is invalid for cipher %s\n", speedOpt.bytes, GetCipherName(speedOpt.cipherId));
        speedret = HITLS_APP_OPT_VALUE_INVALID;
        goto EXIT;
    }

    if (speedOpt.cipherId != 0) {
        if (speedOpt.autoBlock || speedOpt.bytes == 0) {
            RunCipherAutoBlockTest(&speedOpt);
        } else {
            RunCipherBenchmark(&speedOpt);
        }
    }
    
    if (speedOpt.macId != 0) {
        if (speedOpt.autoBlock || speedOpt.bytes == 0) {
            RunMacAutoBlockTest(&speedOpt);
        } else {
            RunMacBenchmark(&speedOpt);
        }
    }

EXIT:
    return speedret;
}

// Command line option processing entry
static int32_t HandleOpt(SpeedCmdOpt* speedOpt)
{
    int32_t ret = ParseSpeedOpts(speedOpt);
    if (ret != HITLS_APP_SUCCESS) {
        return ret;
    }
    return ValidateSpeedOpts(speedOpt);
}

// Parse command line options
static int32_t ParseSpeedOpts(SpeedCmdOpt* speedOpt)
{
    int32_t optType;
    const char* val;
    speedOpt->seconds = SECONDS;
    speedOpt->threads = 1;
    while ((optType = HITLS_APP_OptNext()) != HITLS_APP_OPT_EOF) {
        switch (optType) {
            case HITLS_APP_OPT_EOF: break;
            case HITLS_APP_OPT_ERR:
                AppPrintError("speed: Use -help for summary.\n");
                return HITLS_APP_OPT_UNKOWN;
            case HITLS_APP_OPT_HELP:
                HITLS_APP_OptHelpPrint(g_speedOpts);
                return HITLS_APP_HELP;
            case HITLS_APP_OPT_ENC:
                speedOpt->encTag = 1; break;
            case HITLS_APP_OPT_DEC:
                speedOpt->encTag = 0; break;
            case HITLS_APP_OPT_CIPHER_ALG:
                if ((speedOpt->cipherId = GetCipherId(HITLS_APP_OptGetValueStr())) == -1)
                    {return HITLS_APP_OPT_VALUE_INVALID;} break;
            case HITLS_APP_OPT_MAC:
                if ((speedOpt->macId = GetHMacId(HITLS_APP_OptGetValueStr())) == -1)
                    {return HITLS_APP_OPT_VALUE_INVALID;} break;
            case HITLS_APP_OPT_SECONDS:
                val = HITLS_APP_OptGetValueStr();
                if (val != NULL) {speedOpt->seconds = atoi(val);}
                break;
            case HITLS_APP_OPT_BYTES:
                val = HITLS_APP_OptGetValueStr();
                if (val != NULL) {speedOpt->bytes = atoi(val);}
                break;
            case HITLS_APP_OPT_AUTO_BLOCK:
                speedOpt->autoBlock = true;
                break;
            case HITLS_APP_OPT_THREADS:
                val = HITLS_APP_OptGetValueStr();
                ValidateThreadsParam(speedOpt, val);
                break;
            default:
                AppPrintError("Unknown option type: %d\n", optType);
                return HITLS_APP_OPT_UNKOWN;
        }
    }
    return HITLS_APP_SUCCESS;
}

// Handle the validation of the threads parameter
static void ValidateThreadsParam(SpeedCmdOpt* speedOpt, const char* val)
{
    if (val == NULL) {
        return;
    }
    speedOpt->threads = atoi(val);
    if (speedOpt->threads < 1) {
        speedOpt->threads = 1;
    }
    if (speedOpt->threads > MAX_THREADS) {
        speedOpt->threads = MAX_THREADS;
    }
}

// Validate command line option validity
static int32_t ValidateSpeedOpts(SpeedCmdOpt* speedOpt)
{
    if (HITLS_APP_GetRestOptNum() != 0) {
        AppPrintError("Extra arguments given.\n");
        AppPrintError("enc: Use -help for summary.\n");
        return HITLS_APP_OPT_UNKOWN;
    }

    if (speedOpt->cipherId == 0 && speedOpt->macId == 0) {
        AppPrintError("Please specify either --cipher or --mac algorithm\n");
        return HITLS_APP_OPT_VALUE_INVALID;
    }

    if (speedOpt->seconds <= 0) {
        speedOpt->seconds = SECONDS;
    }

    return HITLS_APP_SUCCESS;
}

// Cipher algorithm benchmark test entry (custom block size)
static void RunCipherBenchmark(const SpeedCmdOpt* opt)
{
    printf("=== Cipher Custom Block Test: %s ===\n", GetCipherName(opt->cipherId));
    RunCipherSingleBlockTest(opt, opt->bytes, 0);
}

// Single block size cipher benchmark test
static void RunCipherSingleBlockTest(const SpeedCmdOpt* opt, int blockSize, int index)
{
    double duration = (opt->seconds > 0) ? opt->seconds : SECONDS;
    bool isEnc = (opt->encTag == 1);
    const char* algName = GetCipherName(opt->cipherId);
    pthread_t* threads = (pthread_t*)malloc(sizeof(pthread_t) * opt->threads);
    ThreadResource* threadRes = (ThreadResource*)malloc(sizeof(ThreadResource) * opt->threads);
    if (threads == NULL || threadRes == NULL) {
        AppPrintError("Failed to allocate memory for threads\n");
        free(threads);
        free(threadRes);
        throughputs[index] = 0.0;
        return;
    }
    for (int i = 0; i < opt->threads; i++) {
        threadRes[i].opt = opt;
        threadRes[i].blockSize = blockSize;
        threadRes[i].threadId = i;
        threadRes[i].threadOps = 0;
        threadRes[i].threadTotalBytes = 0;
        threadRes[i].threadElapsed = 0.0;
        threadRes[i].in = threadRes[i].out = NULL;
        threadRes[i].cipherCtx = NULL;
    }
    TimeF(0);
    for (int i = 0; i < opt->threads; i++) {
        if (pthread_create(&threads[i], NULL, CipherThreadWork, (void*)&threadRes[i]) != 0) {
            AppPrintError("Failed to create cipher thread %d\n", i);
            for (int j = 0; j < i; j++) {
                pthread_join(threads[j], NULL);
            }
            free(threads);
            free(threadRes);
            throughputs[index] = 0.0;
            return;
        }
    }
    for (int i = 0; i < opt->threads; i++) {
        pthread_join(threads[i], NULL);
    }
    double totalSpeed = 0.0;
    uint64_t totalOps = 0;
    double totalElapsed = 0.0;
    AggregateThreadResults(opt, threadRes, &totalSpeed, &totalOps, &totalElapsed);
    printf("Doing %s %s for %.1fs on %d size blocks: %llu %s's in %.2fs ", isEnc ? "Encryption" : "Decryption",
           algName, duration, blockSize, (unsigned long long)totalOps, algName, totalElapsed);
    printf("speed=%.2f KB/s (%.2f MB/s)\n", totalSpeed, totalSpeed / KB_TO_MB_FACTOR);
    throughputs[index] = totalSpeed;
    free(threads);
    free(threadRes);
}

// Automatic multi-block size cipher benchmark test
static void RunCipherAutoBlockTest(const SpeedCmdOpt* opt)
{
    printf("=== Cipher Auto Block Test: %s ===\n", GetCipherName(opt->cipherId));
    for (int i = 0; i < TEST_BLOCK_SIZES_LEN; i++) {
        int blockSize = TEST_BLOCK_SIZES[i];
        RunCipherSingleBlockTest(opt, blockSize, i);
    }

    printf("=== Cipher Performance Summary (units: KB/s) ===\n");
    printf("%-15s", "Algorithm");
    for (int i = 0; i < TEST_BLOCK_SIZES_LEN; i++) {
        printf("%-15d", TEST_BLOCK_SIZES[i]);
    }
    printf("\n%-15s", GetCipherName(opt->cipherId));
    for (int i = 0; i < TEST_BLOCK_SIZES_LEN; i++) {
        printf("%-15.2f", throughputs[i]);
    }
    printf("\n");
}

// Get cipher algorithm key length
static int GetCipherKeyLen(int32_t cipherId)
{
    switch (cipherId) {
        case CRYPT_CIPHER_AES192_CBC: case CRYPT_CIPHER_AES192_CTR:
        case CRYPT_CIPHER_AES192_ECB: case CRYPT_CIPHER_AES192_GCM:
        case CRYPT_CIPHER_AES192_CFB: case CRYPT_CIPHER_AES192_OFB:
            return AES192_KEY_LENGTH;
        case CRYPT_CIPHER_AES256_CBC: case CRYPT_CIPHER_AES256_CTR:
        case CRYPT_CIPHER_AES256_ECB: case CRYPT_CIPHER_AES256_GCM:
        case CRYPT_CIPHER_AES256_CFB: case CRYPT_CIPHER_AES256_OFB:
            return AES256_KEY_LENGTH;
        default:
            return AES128_KEY_LENGTH;
    }
}

// Initialize cipher resources
static int InitCipherResources(ThreadResource* res)
{
    const SpeedCmdOpt* opt = res->opt;
    int threadId = res->threadId;
    int blockSize = res->blockSize;
    bool isEnc = (opt->encTag == 1);

    memset_s(res->key, MAX_KEY_LEN, 0, MAX_KEY_LEN);
    memset_s(res->iv, IV_LENGTH, 0, IV_LENGTH);

    res->in = (unsigned char*)malloc(blockSize);
    res->out = (unsigned char*)malloc(blockSize + ENCRYPT_EXTRA_BUFFER_LEN);
    if (res->in == NULL || res->out == NULL) {
        AppPrintError("Thread %d: Failed to allocate memory\n", threadId);
        free(res->in);
        free(res->out);
        res->in = res->out = NULL;
        return CRYPT_FAILURE;
    }
    memset_s(res->in, blockSize, 0xAA, blockSize);

    res->cipherCtx = CRYPT_EAL_CipherNewCtx(opt->cipherId);
    if (res->cipherCtx == NULL) {
        AppPrintError("Thread %d: Failed to create cipher ctx\n", threadId);
        free(res->in);
        free(res->out);
        res->in = res->out = NULL;
        return CRYPT_FAILURE;
    }

    int keyLen = GetCipherKeyLen(opt->cipherId);
    if (CRYPT_EAL_CipherInit(res->cipherCtx, res->key, keyLen, res->iv, IV_LENGTH, isEnc) != CRYPT_SUCCESS) {
        AppPrintError("Thread %d: Cipher init failed (alg: %s, keyLen: %d)\n",
                      threadId, GetCipherName(opt->cipherId), keyLen);
        FreeCipherResources(res);
        return CRYPT_FAILURE;
    }

    return CRYPT_SUCCESS;
}

// Free cipher resources
static void FreeCipherResources(ThreadResource* res)
{
    memset_s(res->key, MAX_KEY_LEN, 0, MAX_KEY_LEN);
    memset_s(res->iv, IV_LENGTH, 0, IV_LENGTH);
    if (res->cipherCtx != NULL) {
        CRYPT_EAL_CipherFreeCtx(res->cipherCtx);
        res->cipherCtx = NULL;
    }
    if (res->in != NULL) {
        free(res->in);
        res->in = NULL;
    }
    if (res->out != NULL) {
        free(res->out);
        res->out = NULL;
    }
}

// Cipher thread work function
static void* CipherThreadWork(void* arg)
{
    ThreadResource* res = (ThreadResource*)arg;
    const SpeedCmdOpt* opt = res->opt;
    int blockSize = res->blockSize;
    int duration = (opt->seconds > 0) ? opt->seconds : SECONDS;

    if (InitCipherResources(res) != CRYPT_SUCCESS) {
        goto EXIT;
    }

    struct timeval threadStart;
    gettimeofday(&threadStart, NULL);
    uint64_t threadOps = 0;
    size_t threadTotalBytes = 0;

    while (1) {
        struct timeval current;
        gettimeofday(&current, NULL);
        double elapsed = (current.tv_sec - threadStart.tv_sec) +
                         (current.tv_usec - threadStart.tv_usec) / USEC_TO_SEC_FACTOR;
        if (elapsed >= duration) {break;}

        uint32_t outlen = blockSize + ENCRYPT_EXTRA_BUFFER_LEN;
        if (CRYPT_EAL_CipherUpdate(res->cipherCtx, res->in, blockSize, res->out, &outlen) != CRYPT_SUCCESS) {
            AppPrintError("Thread %d: Cipher update failed (block size: %d)\n", res->threadId, blockSize);
            break;
        }
        threadOps++;
        threadTotalBytes += blockSize;
    }

    struct timeval threadEnd;
    gettimeofday(&threadEnd, NULL);
    res->threadOps = threadOps;
    res->threadTotalBytes = threadTotalBytes;
    res->threadElapsed = (threadEnd.tv_sec - threadStart.tv_sec) +
                         (threadEnd.tv_usec - threadStart.tv_usec) / USEC_TO_SEC_FACTOR;

EXIT:
    FreeCipherResources(res);
    return NULL;
}

// MAC algorithm benchmark test entry (custom block size)
static void RunMacBenchmark(const SpeedCmdOpt* opt)
{
    printf("=== MAC Custom Block Test: %s ===\n", GetMacName(opt->macId));
    RunMacSingleBlockTest(opt, opt->bytes, 0);
}

// Single block size MAC benchmark test
static void RunMacSingleBlockTest(const SpeedCmdOpt* opt, int blockSize, int index)
{
    double duration = (opt->seconds > 0) ? opt->seconds : SECONDS;
    const char* algName = GetMacName(opt->macId);
    pthread_t* threads = (pthread_t*)malloc(sizeof(pthread_t) * opt->threads);
    ThreadResource* threadRes = (ThreadResource*)malloc(sizeof(ThreadResource) * opt->threads);
    if (threads == NULL || threadRes == NULL) {
        AppPrintError("Failed to allocate memory for threads\n");
        free(threads);
        free(threadRes);
        throughputs[index] = 0.0;
        return;
    }
    for (int i = 0; i < opt->threads; i++) {
        threadRes[i].opt = opt;
        threadRes[i].blockSize = blockSize;
        threadRes[i].threadId = i;
        threadRes[i].threadOps = 0;
        threadRes[i].threadTotalBytes = 0;
        threadRes[i].threadElapsed = 0.0;
        threadRes[i].data = NULL;
        threadRes[i].macCtx = NULL;
    }
    TimeF(0);
    for (int i = 0; i < opt->threads; i++) {
        if (pthread_create(&threads[i], NULL, MacThreadWork, (void*)&threadRes[i]) != 0) {
            AppPrintError("Failed to create MAC thread %d\n", i);
            for (int j = 0; j < i; j++) {
                pthread_join(threads[j], NULL);
            }
            free(threads);
            free(threadRes);
            throughputs[index] = 0.0;
            return;
        }
    }
    for (int i = 0; i < opt->threads; i++) {
        pthread_join(threads[i], NULL);
    }
    double totalSpeed = 0.0;
    uint64_t totalOps = 0;
    double totalElapsed = 0.0;
    AggregateThreadResults(opt, threadRes, &totalSpeed, &totalOps, &totalElapsed);
    printf("Doing %s for %.1fs on %d size blocks: %llu %s's in %.2fs ",
           algName, duration, blockSize, (unsigned long long)totalOps, algName, totalElapsed);
    printf("speed=%.2f KB/s (%.2f MB/s)\n", totalSpeed, totalSpeed / KB_TO_MB_FACTOR);
    throughputs[index] = totalSpeed;
    free(threads);
    free(threadRes);
}

// Automatic multi-block size MAC benchmark test
static void RunMacAutoBlockTest(const SpeedCmdOpt* opt)
{
    printf("=== MAC Auto Block Test: %s ===\n", GetMacName(opt->macId));
    for (int i = 0; i < TEST_BLOCK_SIZES_LEN; i++) {
        int blockSize = TEST_BLOCK_SIZES[i];
        RunMacSingleBlockTest(opt, blockSize, i);
    }

    printf("=== MAC Performance Summary (units: KB/s) ===\n");
    printf("%-15s", "Algorithm");
    for (int i = 0; i < TEST_BLOCK_SIZES_LEN; i++) {
        printf("%-15d", TEST_BLOCK_SIZES[i]);
    }
    printf("\n%-15s", GetMacName(opt->macId));
    for (int i = 0; i < TEST_BLOCK_SIZES_LEN; i++) {
        printf("%-15.2f", throughputs[i]);
    }
    printf("\n");
}

// Get MAC algorithm key length
static uint32_t GetMacKeyLen(int32_t macId)
{
    switch (macId) {
        case CRYPT_MAC_HMAC_SHA384:
        case CRYPT_MAC_HMAC_SHA512:
            return HMAC_SHA512_KEY_LENGTH;
        default:
            return DEFAULT_HMAC_KEY_LENGTH;
    }
}

// Initialize MAC resources
static int InitMacResources(ThreadResource* res)
{
    const SpeedCmdOpt* opt = res->opt;
    int threadId = res->threadId;
    int blockSize = res->blockSize;

    memset_s(res->key, MAX_KEY_LEN, 0, MAX_KEY_LEN);
    memset_s(res->mac, MAX_MAC_LEN, 0, MAX_MAC_LEN);

    res->data = (unsigned char*)malloc(blockSize);
    if (res->data == NULL) {
        AppPrintError("Thread %d: Failed to allocate memory for MAC test\n", threadId);
        return CRYPT_FAILURE;
    }
    memset_s(res->data, blockSize, 0xAA, blockSize);

    res->macCtx = CRYPT_EAL_MacNewCtx(opt->macId);
    if (res->macCtx == NULL) {
        AppPrintError("Thread %d: Failed to create MAC ctx\n", threadId);
        free(res->data);
        res->data = NULL;
        return CRYPT_FAILURE;
    }

    uint32_t keyLen = GetMacKeyLen(opt->macId);
    if (CRYPT_EAL_MacInit(res->macCtx, res->key, keyLen) != CRYPT_SUCCESS) {
        AppPrintError("Thread %d: MAC init failed\n", threadId);
        FreeMacResources(res);
        return CRYPT_FAILURE;
    }

    return CRYPT_SUCCESS;
}

// Free MAC resources
static void FreeMacResources(ThreadResource* res)
{
    memset_s(res->key, MAX_KEY_LEN, 0, MAX_KEY_LEN);
    memset_s(res->mac, MAX_MAC_LEN, 0, MAX_MAC_LEN);
    if (res->macCtx != NULL) {
        CRYPT_EAL_MacFreeCtx(res->macCtx);
        res->macCtx = NULL;
    }
    if (res->data != NULL) {
        free(res->data);
        res->data = NULL;
    }
}

// MAC thread work function
static void* MacThreadWork(void* arg)
{
    ThreadResource* res = (ThreadResource*)arg;
    const SpeedCmdOpt* opt = res->opt;
    int blockSize = res->blockSize;
    int duration = (opt->seconds > 0) ? opt->seconds : SECONDS;

    if (InitMacResources(res) != CRYPT_SUCCESS) {
        goto EXIT;
    }

    struct timeval threadStart;
    gettimeofday(&threadStart, NULL);
    uint64_t threadOps = 0;
    size_t threadTotalBytes = 0;

    while (1) {
        struct timeval current;
        gettimeofday(&current, NULL);
        double elapsed = (current.tv_sec - threadStart.tv_sec) +
                         (current.tv_usec - threadStart.tv_usec) / USEC_TO_SEC_FACTOR;
        if (elapsed >= duration) {
            break;
        }

        if (CRYPT_EAL_MacUpdate(res->macCtx, res->data, blockSize) != CRYPT_SUCCESS) {
            AppPrintError("Thread %d: MAC update failed\n", res->threadId);
            break;
        }
        threadOps++;
        threadTotalBytes += blockSize;
    }

    uint32_t macLen = sizeof(res->mac);
    if (CRYPT_EAL_MacFinal(res->macCtx, res->mac, &macLen) != CRYPT_SUCCESS) {
        AppPrintError("Thread %d: MAC final failed\n", res->threadId);
        goto EXIT;
    }

    struct timeval threadEnd;
    gettimeofday(&threadEnd, NULL);
    res->threadOps = threadOps;
    res->threadTotalBytes = threadTotalBytes;
    res->threadElapsed = (threadEnd.tv_sec - threadStart.tv_sec) +
                         (threadEnd.tv_usec - threadStart.tv_usec) / USEC_TO_SEC_FACTOR;

EXIT:
    FreeMacResources(res);
    return NULL;
}

// Aggregate thread results
static void AggregateThreadResults(const SpeedCmdOpt* opt, ThreadResource* res,
                                   double* totalSpeed, uint64_t* totalOps, double* totalElapsed)
{
    *totalOps = 0;
    size_t totalBytes = 0;
    *totalElapsed = 0.0;

    for (int i = 0; i < opt->threads; i++) {
        *totalOps += res[i].threadOps;
        totalBytes += res[i].threadTotalBytes;
        if (res[i].threadElapsed > *totalElapsed) {
            *totalElapsed = res[i].threadElapsed;
        }
    }

    if (*totalElapsed <= MIN_ELAPSED_THRESHOLD) {
        *totalElapsed = MIN_ELAPSED_THRESHOLD;
    }
    *totalSpeed = (totalBytes / KB_TO_MB_FACTOR) / *totalElapsed;
}

// Get cipher algorithm ID by name
static int32_t GetCipherId(const char* name)
{
    for (size_t i = 0; i < sizeof(g_cIdList) / sizeof(g_cIdList[0]); i++) {
        if (strcmp(g_cIdList[i].cipherAlgName, name) == 0) {
            return g_cIdList[i].cipherId;
        }
    }
    PrintCipherAlgList();
    return -1;
}

// Get MAC algorithm ID by name
static int32_t GetHMacId(const char* mdName)
{
    for (size_t i = 0; i < sizeof(g_mIdList) / sizeof(g_mIdList[0]); i++) {
        if (strcmp(g_mIdList[i].macAlgName, mdName) == 0) {
            return g_mIdList[i].macId;
        }
    }
    PrintHMacAlgList();
    return -1;
}

// Get cipher algorithm name by ID
static const char* GetCipherName(int32_t cipherId)
{
    for (size_t i = 0; i < sizeof(g_cIdList) / sizeof(g_cIdList[0]); i++) {
        if (g_cIdList[i].cipherId == cipherId) {
            return g_cIdList[i].cipherAlgName;
        }
    }
    return "unknown_cipher";
}

// Get MAC algorithm name by ID
static const char* GetMacName(int32_t macId)
{
    for (size_t i = 0; i < sizeof(g_mIdList) / sizeof(g_mIdList[0]); i++) {
        if (g_mIdList[i].macId == macId) {
            return g_mIdList[i].macAlgName;
        }
    }
    return "unknown_mac";
}

// Print supported cipher algorithm list
static void PrintCipherAlgList(void)
{
    AppPrintError("The current version supports only the following cipher algorithms:\n");
    for (size_t i = 0; i < sizeof(g_cIdList) / sizeof(g_cIdList[0]); i++) {
        AppPrintError("%-19s", g_cIdList[i].cipherAlgName);
        if ((i + 1) % CIPHER_ALG_PER_LINE == 0 && i != sizeof(g_cIdList) / sizeof(g_cIdList[0]) - 1) {
            AppPrintError("\n");
        }
    }
    AppPrintError("\n");
}

// Print supported MAC algorithm list
static void PrintHMacAlgList(void)
{
    AppPrintError("The current version supports only the following digest algorithms:\n");
    for (size_t i = 0; i < sizeof(g_mIdList) / sizeof(g_mIdList[0]); i++) {
        AppPrintError("%-19s", g_mIdList[i].macAlgName);
        if ((i + 1) % CIPHER_ALG_PER_LINE == 0 && i != sizeof(g_mIdList) / sizeof(g_mIdList[0]) - 1) {
            AppPrintError("\n");
        }
    }
    AppPrintError("\n");
}

// Time recording function
static void TimeF(int mode)
{
    if (mode == 0) {
        gettimeofday(&g_startTimeval, NULL);
    }
}

// Block size validity check
static bool CheckBlockSize(int cipherId, int blockSize)
{
    switch (cipherId) {
        case CRYPT_CIPHER_AES128_CBC: case CRYPT_CIPHER_AES192_CBC:
        case CRYPT_CIPHER_AES256_CBC: case CRYPT_CIPHER_SM4_CBC:
        case CRYPT_CIPHER_AES128_ECB: case CRYPT_CIPHER_SM4_ECB:
            return (blockSize % IV_LENGTH) == 0;
        default:
            return blockSize > 0;
    }
}
